package org.xxx.core.aspect;

import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.MethodParameter;
import org.springframework.core.io.InputStreamSource;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.multipart.MultipartFile;
import org.xxx.common.tool.JsonUtil;
import org.xxx.common.tool.StringUtil;
import org.xxx.core.tool.ClassUtil;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

@Aspect
@Configuration
public class RequestLogAspect {

    private static final Logger log = LoggerFactory.getLogger(RequestLogAspect.class);

    /**
     * 控制层 采用  json 数据
     * RESTful API
     */
    @Around("(@within(org.springframework.stereotype.Controller) || @within(org.springframework.web.bind.annotation.RestController))")
    public Object aroundController(ProceedingJoinPoint point) throws Throwable {

        //请求对象
        HttpServletRequest request = Optional
                .ofNullable((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .map(r -> r.getRequest())
                .orElseThrow(() -> new NullPointerException());

        //获取请求参数
        String uri = request.getRequestURI();


        //请求方法
        String httpMethod = request.getMethod();


        StringBuilder beforeReqLog = new StringBuilder(300);

        List<Object> beforeReqArgs = new ArrayList();
        beforeReqLog.append("\n\n================  Request Start  ================\n");
        beforeReqLog.append("===> {}: {}");
        beforeReqArgs.add(httpMethod);
        beforeReqArgs.add(uri);
        this.logIngArgs(point, beforeReqLog, beforeReqArgs);
        this.logIngHeaders(request, beforeReqLog, beforeReqArgs);


        beforeReqLog.append("================   Request End   ================\n");
        long startNs = System.nanoTime();
        log.info(beforeReqLog.toString(), beforeReqArgs.toArray());
        StringBuilder afterReqLog = new StringBuilder(200);
        List<Object> afterReqArgs = new ArrayList();


        afterReqLog.append("\n\n===============  Response Start  ================\n");
        boolean var20 = false;
        Object var13;
        try {
            var20 = true;
            Object result = point.proceed();
            afterReqLog.append("===Result===  {}\n");
            afterReqArgs.add(JSONObject.toJSONString(result));
            var13 = result;
            var20 = false;
        } finally {
            if (var20) {
                long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
                afterReqLog.append("<=== {}: {} ({} ms)\n");
                afterReqArgs.add(httpMethod);
                afterReqArgs.add(uri);
                afterReqArgs.add(tookMs);
                afterReqLog.append("===============   Response End   ================\n");
                log.info(afterReqLog.toString(), afterReqArgs.toArray());
            }
        }

        long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
        afterReqLog.append("<=== {}: {} ({} ms)\n");
        afterReqArgs.add(httpMethod);
        afterReqArgs.add(uri);
        afterReqArgs.add(tookMs);
        afterReqLog.append("===============   Response End   ================\n");

        log.info(afterReqLog.toString(), afterReqArgs.toArray());
        return var13;
    }

    public void logIngArgs(ProceedingJoinPoint point, StringBuilder beforeReqLog, List<Object> beforeReqArgs) {
        MethodSignature ms = (MethodSignature) point.getSignature();
        Method method = ms.getMethod();
        Object[] args = point.getArgs();
        Map<String, Object> paraMap = new HashMap(16);
        Object requestBodyValue = null;

        for (int i = 0; i < args.length; ++i) {
            MethodParameter methodParam = ClassUtil.getMethodParameter(method, i);
            PathVariable pathVariable = (PathVariable) methodParam.getParameterAnnotation(PathVariable.class);
            if (pathVariable == null) {
                RequestBody requestBody = (RequestBody) methodParam.getParameterAnnotation(RequestBody.class);
                String parameterName = methodParam.getParameterName();
                Object value = args[i];
                if (requestBody != null) {
                    requestBodyValue = value;
                } else if (value instanceof HttpServletRequest) {
                    paraMap.putAll(((HttpServletRequest) value).getParameterMap());
                } else if (value instanceof WebRequest) {
                    paraMap.putAll(((WebRequest) value).getParameterMap());
                } else if (!(value instanceof HttpServletResponse)) {
                    String paraName;
                    if (value instanceof MultipartFile) {
                        MultipartFile multipartFile = (MultipartFile) value;
                        paraName = multipartFile.getName();
                        String fileName = multipartFile.getOriginalFilename();
                        paraMap.put(paraName, fileName);
                    } else if (value instanceof MultipartFile[]) {
                        MultipartFile[] arr = (MultipartFile[]) ((MultipartFile[]) value);
                        if (arr.length != 0) {
                            paraName = arr[0].getName();
                            StringBuilder sb = new StringBuilder(arr.length);
                            MultipartFile[] var28 = arr;
                            int var19 = arr.length;

                            for (int var20 = 0; var20 < var19; ++var20) {
                                MultipartFile multipartFile = var28[var20];
                                sb.append(multipartFile.getOriginalFilename());
                                sb.append(",");
                            }

                            paraMap.put(paraName, StringUtil.removeSuffix(sb.toString(), ","));
                        }
                    } else {
                        if (value instanceof List) {
                            List<?> list = (List) value;
                            AtomicBoolean isSkip = new AtomicBoolean(false);
                            Iterator var17 = list.iterator();

                            while (var17.hasNext()) {
                                Object o = var17.next();
                                if ("StandardMultipartFile".equalsIgnoreCase(o.getClass().getSimpleName())) {
                                    isSkip.set(true);
                                    break;
                                }
                            }

                            if (isSkip.get()) {
                                paraMap.put(parameterName, "此参数不能序列化为json");
                                continue;
                            }
                        }

                        RequestParam requestParam = (RequestParam) methodParam.getParameterAnnotation(RequestParam.class);
                        paraName = parameterName;
                        if (requestParam != null && StringUtil.isNotBlank(requestParam.value())) {
                            paraName = requestParam.value();
                        }

                        if (value == null) {
                            paraMap.put(paraName, (Object) null);
                        } else if (ClassUtil.isPrimitiveOrWrapper(value.getClass())) {
                            paraMap.put(paraName, value);
                        } else if (value instanceof InputStream) {
                            paraMap.put(paraName, "InputStream");
                        } else if (value instanceof InputStreamSource) {
                            paraMap.put(paraName, "InputStreamSource");
                        } else if (JsonUtil.canSerialize(value)) {
                            paraMap.put(paraName, value);
                        } else {
                            paraMap.put(paraName, "此参数不能序列化为json");
                        }
                    }
                }
            }
        }

        if (paraMap.isEmpty()) {
            beforeReqLog.append("\n");
        } else {
            beforeReqLog.append(" Parameters: {}\n");
            beforeReqArgs.add(JSONObject.toJSONString(paraMap));
        }

        if (requestBodyValue != null) {
            beforeReqLog.append("====Body=====  {}\n");
            beforeReqArgs.add(JSONObject.toJSONString(requestBodyValue));
        }

    }

    public void logIngHeaders(HttpServletRequest request, StringBuilder beforeReqLog, List<Object> beforeReqArgs) {
        Enumeration headers = request.getHeaderNames();
        while (headers.hasMoreElements()) {
            String headerName = (String) headers.nextElement();
            String headerValue = request.getHeader(headerName);
            beforeReqLog.append("===Headers===  {}: {}\n");
            beforeReqArgs.add(headerName);
            beforeReqArgs.add(headerValue);
        }
    }
}
